﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xaml;
using System.Xml;
using System.CodeDom;
using System.Windows;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.ComponentModel;
using NeuroSpeech.XamlExtensions;

namespace NeuroSpeech.UIAtoms.Generator
{
    public class WPFXamlWriter : XamlWriter
    {

        public WPFXamlWriter(XamlSchemaContext context = null)
        {
            _context = context ?? new XamlSchemaContext();
        }

        private int varCount = 1;

        private CodeNamespace Result = new CodeNamespace();
        private CodeTypeDeclaration TypeDeclaration = new CodeTypeDeclaration();
        private CodeMemberMethod InitMethod = new CodeMemberMethod();
        private List<CodeStatement> Statements = new List<CodeStatement>();
        private List<CodeStatement> PostStatements = new List<CodeStatement>();
        private List<CodeStatement> PreStatements = new List<CodeStatement>();

        private Dictionary<string, XamlItem> namedObjects = new Dictionary<string, XamlItem>();


        //private XamlType lastType = null;

        private List<NamespaceDeclaration> declarations = new List<NamespaceDeclaration>();

        private XamlSchemaContext _context;
        public override XamlSchemaContext SchemaContext
        {
            get
            {
                return _context;
            }
        }

        public Stack<XamlItem> Stack
        {
            get
            {
                return stack;
            }
        }

        public Dictionary<string, XamlItem> NamedObjects
        {
            get
            {
                return namedObjects;
            }
        }

        private Stack<XamlItem> stack = new Stack<XamlItem>();
        private XamlItem Peek
        {
            get
            {
                return stack.Peek();
            }
        }


        private static XamlDirectiveWriter[] Writers = new XamlDirectiveWriter[] { 
            new ItemsWriter(),
            new ClassWriter(),
            new KeyWriter(),
            new NameWriter(),
            new PositionalParametersWriter(),
            new BasePathWriter(),
            new InitializerWriter()
        };

        private static DefaultWriter Writer = new DefaultWriter();

        

        private static Type[] styleTypes = new Type[] { 
            typeof(Style),
            typeof(FrameworkTemplate)
        };

        #region Method ToString
        private static string ToString(XamlXmlReader reader)
        {
            if (reader.Member != null)
            {
                return string.Format(
                    "{0},{1},{2}",
                    reader.Member.Name,
                    reader.Member.UnderlyingMember == null ? "" : reader.Member.UnderlyingMember.Name,
                    reader.Member.PreferredXamlNamespace);
            }
            if (reader.Namespace != null)
                return reader.Namespace.Namespace;
            if (reader.Type != null)
                return reader.Type.Name;
            return "";
        }

        #endregion

        #region Method Parse

        public CodeNamespace Parse(string inputFile) {
            return Parse(new StreamReader(inputFile));
        }

        public CodeNamespace Parse(TextReader stream)
        {

            Result.Imports.Add(new CodeNamespaceImport("System"));
            //Result.Imports.Add(new CodeNamespaceImport("System.Windows"));
            //Result.Imports.Add(new CodeNamespaceImport("System.Windows.Controls"));
            Result.Types.Add(TypeDeclaration);
            InitMethod.Attributes = MemberAttributes.Private;
            InitMethod.Name = "InitializeComponent";
            TypeDeclaration.Members.Add(InitMethod);


            XmlReader xr = XmlReader.Create(stream);
            XamlXmlReader reader = new XamlXmlReader(xr,this.SchemaContext);




            while (reader.Read())
            {
                PrintPeek(reader);
                switch (reader.NodeType)
                {
                    case XamlNodeType.EndMember:
                        this.WriteEndMember();
                        break;
                    case XamlNodeType.EndObject:
                        this.WriteEndObject();
                        break;
                    case XamlNodeType.GetObject:
                        this.WriteGetObject();
                        break;
                    case XamlNodeType.NamespaceDeclaration:
                        this.WriteNamespace(reader.Namespace);
                        break;
                    case XamlNodeType.None:
                        break;
                    case XamlNodeType.StartMember:
                        this.WriteStartMember(reader.Member);
                        break;
                    case XamlNodeType.StartObject:
                        if (IsStyleElement(reader.Type))
                        {
                            XamlType type = reader.Type;
                            XamlReader r = reader.ReadSubtree();
                            StringWriter sw = new StringWriter();
                            XamlWriter w = new XamlXmlWriter(sw, this.SchemaContext);
                            XamlServices.Transform(r, w);
                            this.WriteXamlValue(type, sw.ToString());
                        }
                        else
                        {
                            this.WriteStartObject(reader.Type);
                        }
                        break;
                    case XamlNodeType.Value:
                        this.WriteValue(reader.Value);
                        break;
                    default:
                        break;
                }

            }

            InitMethod.Statements.Add(new CodeCommentStatement { 
                Comment = new CodeComment("Pre Statements...")
            });

            foreach (CodeStatement stmt in PreStatements)
            {
                InitMethod.Statements.Add(stmt);
            }

            InitMethod.Statements.Add(new CodeCommentStatement
            {
                Comment = new CodeComment("Statements...")
            });

            foreach (CodeStatement stmt in Statements)
            {
                InitMethod.Statements.Add(stmt);
            }



            InitMethod.Statements.Add(new CodeCommentStatement { 
                Comment = new CodeComment("Post Statements...")
            });

            foreach(CodeStatement cs in PostStatements){
                InitMethod.Statements.Add(cs);
            }

            return Result;
        }

        #region private void PrintPeek(Stack<XamlItem> stack,XamlXmlReader reader)
        private void PrintPeek(XamlXmlReader reader)
        {
            Stack<XamlItem> stack = Stack;
            if (stack.Count > 0)
            {
                XamlItem peek = stack.Peek();
                Trace.WriteLine(string.Format("{0}{1}:{2} {3}", peek.Depth, reader.NodeType, reader.Value, ToString(reader)));
            }
            else
            {
                Trace.WriteLine(string.Format("{0}:{1} {2}",reader.NodeType, reader.Value, ToString(reader)));
            }
        }
        #endregion


 
        #endregion

        #region Method IsStyleElement
        private bool IsStyleElement(XamlType xamlMember)
        {
            if (xamlMember.UnderlyingType == null)
                return false;
            Type t = xamlMember.UnderlyingType;
            if (styleTypes.Any(x => x.IsAssignableFrom(t)))
                return true;
            return false;
        }

        #endregion

        #region Method WriteEndMember
        public override void WriteEndMember()
        {
            //Trace.WriteLine(string.Format("EndMember: {0}", Peek.Member.Name));
            XamlCodeContext context = GetContext();
            ExecuteWriters(x=>x.EndMember(context));
            if(Stack.Count>0)
                Peek.Member = null;
            Peek.Value.Clear();
        }

        public StringBuilder Warnings = new StringBuilder();

        #region private void ExecuteWriters()
        private void ExecuteWriters(Action<XamlDirectiveWriter> action)
        {
            if (Stack.Count == 0)
                return;
            XamlItem peek = Peek;
            if (peek.Member!=null && peek.Member.IsDirective)
            {

                XamlDirectiveWriter w = Writers.FirstOrDefault(x => IsPeek(x.Directive));
                if (w != null) {
                    action(w);
                    return;
                }
                Trace.WriteLine("Writer not found for .. "+peek.Member.Name);
                Warnings.AppendLine("Writer not found for .. " + peek.Member.Name);
            }

            action(Writer);
        }
        #endregion
 
        #endregion

        #region Method WriteEndObject
        public override void WriteEndObject()
        {
            if (Peek.Name != null)
            {
                namedObjects.Add(Peek.Name, stack.Peek());
            }

            XamlCodeContext context = GetContext();
            ExecuteWriters(x=>x.EndObject(context));

            XamlItem peek = Peek;


            stack.Pop();


            AddSupportInitialize(peek, true);

        }

        private XamlCodeContext GetContext()
        {
            return new XamlCodeContext(this.SchemaContext,this.Warnings, Stack, Result, TypeDeclaration, Statements, PreStatements, PostStatements); ;
        }

        private void AddSupportInitialize(XamlItem item, bool end = false)
        {
            if (!item.SupportsInitialize)
                return;

            if (end && !item.SupportInitializeAdded)
                return;

            // if its end and if last is same... then remove last...
            // remove unnecessary begin/end init without any child statements...
            if (end) {
                if (RemoveLastInitialize(item))
                    return;
            }

            item.SupportInitializeAdded = true;

            CodeExpression target;
            if (item.IsRoot)
            {
                target = new CodeThisReferenceExpression();
            }
            else
            {
                target = new CodeVariableReferenceExpression(item.Variable);
            }
            CodeExpressionStatement ces = new CodeExpressionStatement
            {
                Expression = new CodeMethodInvokeExpression
                {
                    Method = new CodeMethodReferenceExpression
                    {
                        TargetObject = target,
                        MethodName = end ? "EndInit" : "BeginInit"
                    }
                }
            };
            Statements.Add(ces);
        }

        private bool RemoveLastInitialize(XamlItem item)
        {
            CodeExpressionStatement last = Statements.Last() as CodeExpressionStatement;
            if (last == null)
                return false;
            CodeMethodInvokeExpression mi = last.Expression as CodeMethodInvokeExpression;
            if (mi == null)
                return false;
            if (mi.Method.MethodName != "BeginInit")
                return false;
            CodeVariableReferenceExpression ve = mi.Method.TargetObject as CodeVariableReferenceExpression;
            if (ve == null)
                return false;
            if (ve.VariableName != item.Variable) 
                return false;
            Statements.Remove(last);
            return true;
        } 

        #endregion



        #region Method WriteGetObject
        public override void WriteGetObject()
        {
            XamlItem getItem = new XamlItem();
            getItem.Type = Peek.Member.Type;
            getItem.Parent = Peek;
            //getItem.TargetObject = getItem.Parent.TargetObject;
            stack.Push(getItem);
        } 
        #endregion

        #region Method WriteNamespace
        public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
        {
            declarations.Add(namespaceDeclaration);
        } 
        #endregion

        #region Method WriteStartMember
        public override void WriteStartMember(XamlMember member)
        {
            //Trace.WriteLine(string.Format("StartMember: {0}",member.Name));
            Peek.Member = member;
            if (!Peek.IsRoot)
            {
                if (Peek.Variable != null)
                {
                    Peek.TargetObject =
                        new CodePropertyReferenceExpression
                        {
                            TargetObject = new CodeVariableReferenceExpression(Peek.Variable),
                            PropertyName = member.Name
                        };
                }
                else
                {
                    Peek.TargetObject = new CodePropertyReferenceExpression { 
                        TargetObject = Peek.Parent.TargetObject,
                        PropertyName = member.Name
                    };
                }
            }
            else
            {
                Peek.TargetObject =
                    new CodeFieldReferenceExpression
                    {
                        TargetObject = new CodeThisReferenceExpression(),
                        FieldName = member.Name
                    };
            }

            XamlCodeContext context = GetContext();
            ExecuteWriters(x => x.StartMember(context));

        }

        #endregion

        #region Method WriteStartObject
        public override void WriteStartObject(XamlType type)
        {
            XamlItem item = new XamlItem();
            item.Type = type;
            if (stack.Count == 0)
            {
                item.IsRoot = true;
            }
            else
            {
                item.Parent = Peek;
                item.Variable = CreateVariable(type);

                item.Parent.Value.SetValue(new CodeVariableReferenceExpression { VariableName=item.Variable }, item);

                Peek.Value.ValueType = type.UnderlyingType;
            }
            stack.Push(item);

            XamlCodeContext context = GetContext();
            ExecuteWriters(x => x.StartObject(context));
            AddSupportInitialize(item);
        }

        #endregion

        private string CreateVariable(XamlType type) {
            
            string name = type.UnderlyingType!=null ? type.UnderlyingType.Name : type.Name;
            name = Char.ToLower(name[0]) + name.Substring(1);
            return string.Format("{0}{1}",name,varCount++);
        }

        #region Method WriteValue
        public override void WriteValue(object value)
        {
            Peek.Value.SetValue(value);
            if (Peek.Value.ValueType == null)
                Peek.Value.ValueType = typeof(string);
        } 
        #endregion

        #region Method IsPeek
        private bool IsPeek(XamlDirective dir)
        {
            XamlItem peek = stack.Peek();
            XamlMember contentProperty = peek.Type.GetAliasedProperty(dir);
            if (peek.Member == contentProperty ||
                peek.Member == dir)
            {
                return true;
            }
            return false;
        } 
        #endregion


        


        private void WriteXamlValue(XamlType type, string p)
        {
            /*CodeCommentStatement css = new CodeCommentStatement
            {
                Comment = new CodeComment { 
                    Text =  Peek.TargetObjectString + " = " + type.Name
                }
            };
            this.Statements.Add(css);*/

            CodeMethodInvokeExpression mi = new CodeMethodInvokeExpression
            {
                Method = new CodeMethodReferenceExpression { 
                    TargetObject = new CodeTypeReferenceExpression("System.Windows.Markup.XamlReader"),
                    MethodName = "Load"
                }
            };

            if (IsProjectSilverlight)
            {
                mi.Parameters.Add(new CodePrimitiveExpression(p));
            }
            else
            {
                CodeObjectCreateExpression sr = 
                    new CodeObjectCreateExpression (typeof(System.IO.StringReader), new CodePrimitiveExpression(p));
                CodeMethodInvokeExpression xr = new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression
                    {
                        MethodName = "Create",
                        TargetObject = new CodeTypeReferenceExpression {
                            Type = new CodeTypeReference(typeof(System.Xml.XmlReader))
                        }
                    },
                    sr
                    );

                mi.Parameters.Add(xr);
            }

            CodeAssignStatement cas = new CodeAssignStatement
            {
                Left = Peek.TargetObject,
                Right = new CodeCastExpression { 
                    TargetType = new CodeTypeReference(type.UnderlyingType),
                    Expression = mi
                }
            };

            Statements.Add(cas);

        }



        public bool IsProjectSilverlight { get; set; }
    }





}
